﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Collections.Specialized;
using Jobping.StickyBeak.Config;

namespace Jobping.StickyBeak.HttpModule
{
	public class WebRequestBuilder
	{
		private static string SessionKey = "sbs";
		private static string ComputerKey = "sbb";

		private Func<string, bool> ignoreHeader, ignoreQueryString, ignoreFormKey, ignoreCookie, ignoreSession;

		public WebRequestBuilder(
			Func<string, bool> ignoreHeader,
			Func<string, bool> ignoreQueryString,
			Func<string, bool> ignoreFormKey,
			Func<string, bool> ignoreCookie,
			Func<string, bool> ignoreSession)
		{
			this.ignoreHeader = ignoreHeader;
			this.ignoreQueryString = ignoreQueryString;
			this.ignoreFormKey = ignoreFormKey;
			this.ignoreCookie = ignoreCookie;
			this.ignoreSession = ignoreSession;
		}

		public RequestLog Build(HttpContextBase context)
		{
			this.Context = context;

			var log = new RequestLog() { Id = GetAUniqueId() };
			log.DateUtc = DateTime.UtcNow;

			var request = this.Context.Request;

			log.Url = request.RawUrl.ToString();
			log.IPAddress = request.UserHostAddress;
			log.SessionId =  this.GetSessionId();
			log.BrowserId = this.GetComputerId();
			log.HttpMethod = context.Request.HttpMethod;
			log.Username = context.User == null ? null : context.User.Identity.Name;
			log.Referral = request.UrlReferrer == null ? null : request.UrlReferrer.ToString();

			log.HeaderValues = GetRequestLogValues(request.Headers, RequestLogValueSource.Header, this.ignoreHeader);
			log.QueryStringValues = GetRequestLogValues(request.QueryString, RequestLogValueSource.QueryString, ignoreQueryString);
			log.FormValues = GetRequestLogValues(request.Form, RequestLogValueSource.Form, this.ignoreFormKey);
			log.CookieValues= GetRequestLogValues(request.Cookies, RequestLogValueSource.QueryString, this.ignoreCookie);
			log.SessionValues = GetRequestLogValues(context.Session, RequestLogValueSource.Session, this.ignoreSession);
			return log;
		}

		protected RequestLogValue[] GetRequestLogValues(HttpCookieCollection cookies, RequestLogValueSource source, Func<string, bool> ignoreItem)
		{
			var items = new List<RequestLogValue>();

			foreach (var key in cookies.AllKeys)
			{
				//We don't log empty keys
				if (String.IsNullOrEmpty(key) || String.IsNullOrEmpty(key.Trim())) continue;

				if (ignoreItem(key)) continue;

				var item = new RequestLogValue() { Source = source };
				item.Key = key;
				item.Value = cookies[key].Value;
				items.Add(item);
			}

			return items.ToArray();
		}

		protected RequestLogValue[] GetRequestLogValues(NameValueCollection values, RequestLogValueSource source, Func<string, bool> ignoreItem)
		{
			var items = new List<RequestLogValue>();

			foreach (var key in values.AllKeys)
			{
				//We don't log empty keys
				if(String.IsNullOrEmpty(key) || String.IsNullOrEmpty(key.Trim())) continue;

				if (ignoreItem(key)) continue;

				var item = new RequestLogValue() { Source = source };
				item.Key = key;
				item.Value = values[key];
				items.Add(item);
			}

			return items.ToArray();
		}

		protected RequestLogValue[] GetRequestLogValues(HttpSessionStateBase values, RequestLogValueSource source, Func<string, bool> ignoreItem)
		{
			if (values == null)
				return new RequestLogValue[0];

			var items = new List<RequestLogValue>();

			foreach (string key in values.Keys)
			{
				//We don't log empty keys
				if (String.IsNullOrEmpty(key) || String.IsNullOrEmpty(key.Trim())) continue;

				if (ignoreItem(key)) continue;

				var item = new RequestLogValue() { Source = source };
				var itemValue = values[key];
				item.Key = key;
				item.Value = itemValue == null ? "<--!NULL!-->" : itemValue.ToString();
				items.Add(item);
			}

			return items.ToArray();
		}


		private string GetSessionId()
		{
			string sessionId = GetCookieValue(SessionKey);

			if (!String.IsNullOrEmpty(sessionId))
				return sessionId;

			sessionId = GetAUniqueId();
			SetCookie(SessionKey, sessionId, null);

			return sessionId;
		}

		private string GetComputerId()
		{
			string computerId = GetCookieValue(ComputerKey);

			if (!String.IsNullOrEmpty(computerId))
				return computerId;

			computerId = GetAUniqueId();
			SetCookie(ComputerKey, computerId, new TimeSpan(1000,0,0,0,0));

			return computerId;
		}

		public HttpContextBase Context { get; protected set; }

		private void SetCookie(string key, string value, TimeSpan? expires)
		{
			//Encode Parts
			key = this.Context.Server.UrlEncode(key);
			value = this.Context.Server.UrlEncode(value);

			HttpCookie cookie = new HttpCookie(key, value);

			if(expires.HasValue)
				cookie.Expires = DateTime.Now.Add(expires.Value);

			this.Context.Response.Cookies.Set(cookie);
		}

		private string GetCookieValue(string key)
		{
			string value = null;
			HttpCookie cookie = GetCookie(key);

			if (cookie == null) return null;

			value = cookie.Value;

			//decode stored value
			value = this.Context.Server.UrlDecode(value);
			return value;
		}

		private HttpCookie GetCookie(string key)
		{
			//encode key for retrieval
			key = this.Context.Server.UrlEncode(key);
			return this.Context.Request.Cookies.Get(key);
		}

		private static string GetAUniqueId()
		{
			return ShortEncode(Guid.NewGuid());
		}

		private static string ShortEncode(Guid guid)
		{
			string enc = Convert.ToBase64String(guid.ToByteArray());
			enc = enc.Replace("/", "_");
			enc = enc.Replace("+", "-");
			return enc.Substring(0, 22);
		}
	}
}
